(function() {
  var Gun = typeof window !== 'undefined' ? window.Gun : require('../gun');
  var ify = Gun.node.ify,
    u;
  Gun.chain.time = function(data, a, b) {
    if (data instanceof Function) {
      return travel(data, a, b, this);
    }
    var gun = this;
    //root = gun.back(-1);
    var cb = (a instanceof Function && a) || (b instanceof Function && b);
    if (Gun.is(data)) {
      data.get(function(soul) {
        if (!soul) {
          return cb && cb({ err: 'Timegraph cannot link `undefined`!' });
        }
        gun.time(Gun.val.link.ify(soul), a, b);
      }, true);
      return gun;
    }
    var opt = cb === a ? b : a;
    opt = Gun.text.is(opt) ? { key: opt } : opt || {};
    // eslint-disable-next-line no-useless-escape
    var t = new Date(Gun.state()).toISOString().split(/[\-t\:\.z]/gi);
    var p,
      tmp = t.pop();
    gun.get(function(soul) {
      var id = soul;
      p = id;
      if (!p) {
        id = p = (gun.back('opt.uuid') || Gun.text.random)(9);
      }
      // could shrink this into a loop. Do later?
      t = [p].concat(t);
      var rid = opt.key || (gun.back('opt.uuid') || Gun.text.random)(9);
      var milli = ify({}, t.join(':'));
      milli[rid] = data;
      tmp = t.pop();
      var sec = ify({}, t.join(':'));
      sec[tmp] = milli;
      tmp = t.pop();
      var min = ify({}, t.join(':'));
      min[tmp] = sec;
      tmp = t.pop();
      var hour = ify({}, t.join(':'));
      hour[tmp] = min;
      tmp = t.pop();
      var day = ify({}, t.join(':'));
      day[tmp] = hour;
      tmp = t.pop();
      var month = ify({}, t.join(':'));
      month[tmp] = day;
      tmp = t.pop();
      var year = ify({}, t.join(':'));
      year[tmp] = month;
      tmp = t.pop();
      var time = ify({}, t.join(':') || id);
      time[tmp] = year;
      gun.put(time, cb);
    }, true);
    return gun;
  };
  function travel(cb, opt, b, gun) {
    var root = gun.back(-1);
    //tmp;
    (opt = Gun.num.is(opt) ? { last: opt } : opt || {}).seen = opt.seen || {};
    var t = now(opt.start);
    gun.on(function(data, key, msg, eve) {
      var at = msg.$._,
        id = at.link || at.soul || Gun.node.soul(data);
      if (!id) {
        return;
      }
      if (false === opt.next) {
        eve.off();
      } else {
        opt.next = true;
      }
      opt.start = [(opt.id = id)].concat(t);
      opt.low = opt.low || opt.start;
      opt.top = opt.top || opt.start;
      opt.now = [id].concat(now());
      //console.log("UPDATE");
      find(opt, cb, root, opt.at ? opt.now : (opt.at = opt.start));
    });
    return gun;
  }
  function now(t) {
    return (
      new Date(t || Gun.state())
        .toISOString()
        // eslint-disable-next-line no-useless-escape
        .split(/[\-t\:\.z]/gi)
        .slice(0, -1)
    );
  }
  function find(o, cb, root, at, off) {
    at = at || o.at;
    var t = at.join(':'),
      tmp;
    if (!off) {
      if (o.seen[t]) {
        return;
      }
      o.seen[t] = true;
    }
    var next = (o.low || o.start)[at.length];
    root.get(t).get(function(msg, ev) {
      if (off) {
        ev.off();
      }
      //var g = this;
      //console.log(at, msg.put);
      if (u === msg.put) {
        find(o, cb, root, at.slice(0, -1), off);
        return;
      }
      if (7 < at.length) {
        var l = Object.keys(msg.put).length;
        if (l === o.seen[t]) {
          return;
        }
        var when = +toDate(at);
        Gun.node.is(msg.put, function(v, k) {
          cb(v, k, when, ev);
          if (o.last) {
            --o.last;
          }
        });
        o.seen[t] = l;
        if (!o.last) {
          return;
        }
        if (o.last <= 0) {
          return;
        }
        o.low = at;
        tmp = at.slice(0, -1);
        find(o, cb, root, tmp, true);
        return;
      }
      if (o.last && false !== off) {
        var keys = Object.keys(msg.put)
          .sort()
          .reverse();
        var less = Gun.list.map(keys, function(k) {
          if (parseFloat(k) < parseFloat(next)) {
            return k;
          }
        });
        if (!less) {
          find(o, cb, root, at.slice(0, -1), true);
        } else {
          tmp = (at || o.at).slice();
          tmp.push(less);
          (o.low = tmp.slice()).push(Infinity);
          find(o, cb, root, tmp, true);
        }
      }
      if (off) {
        return;
      }
      if (!o.next) {
        return;
      }
      if (at < o.start.slice(0, at.length)) {
        return;
      }
      var n = [o.id].concat(now()),
        top = n[at.length];
      Gun.node.is(msg.put, function(v, k) {
        if (k > top) {
          return;
        }
        (v = at.slice()).push(k);
        find(o, cb, root, v, false);
      });
    });
  }
  function toDate(at) {
    at = at.slice(-7);
    return new Date(
      Date.UTC(at[0], parseFloat(at[1]) - 1, at[2], at[3], at[4], at[5], at[6])
    );
  }
})();
